#include<graphics.h>
#include<iostream>
#include<string>
#include<vector>
#include<cmath>

int idx_current_anim = 0;
const int PLAYER_ANIM_NUM = 6;
IMAGE img_Johnny_left[PLAYER_ANIM_NUM];
IMAGE img_Johnny_right[PLAYER_ANIM_NUM];
IMAGE img_Tusk_up;
IMAGE img_Tusk_down;
IMAGE img_Tusk_right;
IMAGE img_Tusk_left;

const int WINDOW_WIDTH = 1920;
const int WINDOW_HEIGHT = 1080;

const int BUTTON_WIDTH = 259;
const int BUTTON_HEIGHT = 260;

#pragma comment(lib,"Winmm.lib")
#pragma comment(lib,"MSIMG32.LIB")

bool running = true;
bool is_game_started = false;

inline void putimage_alpha(int x, int y, IMAGE* img) {
    int w = img->getwidth();
    int h = img->getheight();
    AlphaBlend(GetImageHDC(NULL), x, y, w, h, GetImageHDC(img), 0, 0, w, h, { AC_SRC_OVER,0,255,AC_SRC_ALPHA });
}
class Animation
{
public:
    Animation(LPCTSTR path, int num, int interval)
    {
        interval_ms = interval;
        TCHAR path_file[256];
        for (size_t i = 0; i < num; i++)
        {
            _stprintf_s(path_file, path, i);
            IMAGE* frame = new IMAGE();
            loadimage(frame, path_file);
            frame_list.push_back(frame);
        }
    }
    ~Animation()
    {
        for (size_t i = 0; i < frame_list.size(); i++)
            delete frame_list[i];
    }
    void Play(int x, int y, int delta)
    {
        timer += delta;
        if (timer >= interval_ms)
        {
            idx_frame = (idx_frame + 1) % frame_list.size();
            timer = 0;
        }
        putimage_alpha(x, y, frame_list[idx_frame]);
    }

private:
    int timer = 0;
    int idx_frame = 0;
    int interval_ms = 0;
    std::vector<IMAGE*> frame_list;
};
class Vector2
{
public:
    float x = 0;
    float y = 0;
public:
    Vector2() = default;
    ~Vector2() = default;
    Vector2(float x, float y)
        :x(x), y(y) {}
    Vector2 operator+(const Vector2& vec)const
    {
        return Vector2(x + vec.x, y + vec.y);
    }
    void operator+=(const Vector2& vec)
    {
        x += vec.x, y += vec.y;
    }
    void operator-=(const Vector2& vec)
    {
        x -= vec.x, y -= vec.y;
    }
    Vector2 operator-(const Vector2& vec)const
    {
        return Vector2(x - vec.x, y - vec.y);
    }
    float operator*(const Vector2& vec)const
    {
        return x * vec.x + y * vec.y;
    }
    Vector2 operator*(float val)const
    {
        return Vector2(x * val, y * val);
    }
    void operator*=(float val)
    {
        x *= val, y *= val;
    }
    float length()
    {
        return sqrt(x * x + y * y);
    }
    Vector2 normalize()
    {
        float len = length();
        if (len == 0)
            len = 0.0001f;
        return Vector2(x / len, y / len);
    }
};
class Button
{
public:
    Button(RECT rect, LPCTSTR path_img_idle, LPCTSTR path_img_hovered, LPCTSTR path_img_pushed)
    {
        region = rect;
        loadimage(&img_idle, path_img_idle);
        loadimage(&img_hovered, path_img_hovered);
        loadimage(&img_pushed, path_img_pushed);
    }
    ~Button() = default;
    void Draw()
    {
        switch (status)
        {
        case Status::Idle:
            putimage_alpha(region.left, region.top, &img_idle);
            break;
        case Status::Hovered:
            putimage_alpha(region.left, region.top, &img_hovered);
            break;
        case Status::Pushed:
            putimage_alpha(region.left, region.top, &img_pushed);
            break;
        }
    }
    void ProcessEvent(const ExMessage& msg)
    {
        switch (msg.message)
        {
        case WM_MOUSEMOVE:
            if (status == Status::Idle && CheckCursorHit(msg.x, msg.y))
                status = Status::Hovered;
            else if (status == Status::Hovered && !CheckCursorHit(msg.x, msg.y))
                status = Status::Idle;  
        case WM_LBUTTONDOWN:
            if (CheckCursorHit(msg.x, msg.y))
                status = Status::Pushed;
            break;
        case WM_LBUTTONUP:
            if (CheckCursorHit(msg.x, msg.y))
                OnClick();
            status = Status::Hovered;  
            break;
        }
    }
protected:
    virtual void OnClick() = 0;
private:
    enum class Status
    {
        Idle = 0, Hovered, Pushed
    };
private:
    RECT region;
    IMAGE img_idle;
    IMAGE img_hovered;
    IMAGE img_pushed;
    Status status = Status::Idle;
private:
    bool CheckCursorHit(int x, int y)
    {
        return x >= region.left && x <= region.right && y >= region.top && y <= region.bottom;
    }
};
class StartGameButton :public Button
{
public:
    StartGameButton(RECT rect, LPCTSTR path_img_idle, LPCTSTR path_img_hovered, LPCTSTR path_img_pushed)
        :Button(rect, path_img_idle, path_img_hovered, path_img_pushed) {}
    ~StartGameButton() = default;
protected:
    void OnClick()
    {
        is_game_started = true;
        mciSendString(_T("play gamebgm repeat from 0"), NULL, 0, NULL);
    }
};
class QuitGameButton :public Button
{
public:
    QuitGameButton(RECT rect, LPCTSTR path_img_idle, LPCTSTR path_img_hovered, LPCTSTR path_img_pushed)
        :Button(rect, path_img_idle, path_img_hovered, path_img_pushed) {}
    ~QuitGameButton() = default;
protected:
    void OnClick()
    {
        running = false;
    }
};
class Player
{
public:
    const int PLAYER_WIDTH = 140;
    const int PLAYER_HEIGHT = 100;
public:
    Player()
    {
        anim_left = new Animation(_T("img/Johnny_left_%d.png"), 6, 45);
        anim_right = new Animation(_T("img/Johnny_right_%d.png"), 6, 45);
    }
    ~Player()
    {
        delete anim_left;
        delete anim_right;
    }
    void ProcessEvent(const ExMessage& msg)
    {
        switch (msg.message)
        {
        case WM_KEYDOWN:
            switch (msg.vkcode)
            {
            case 'W':
                is_move_up = true;
                break;
            case 'S':
                is_move_down = true;
                break;
            case 'A':
                is_move_left = true;
                break;
            case 'D':
                is_move_right = true;
                break;
            }
            break;
        case WM_KEYUP:
            switch (msg.vkcode)
            {
            case 'W':
                is_move_up = false;
                break;
            case 'S':
                is_move_down = false;
                break;
            case 'A':
                is_move_left = false;
                break;
            case 'D':
                is_move_right = false;
                break;
            }
            break;
        case WM_LBUTTONDOWN:
            is_fire_key_down = true;
            break;
        case WM_LBUTTONUP:
            is_fire_key_down = false;
            break;
        }
    }
    void Move()
    {
        int dir_x = is_move_right - is_move_left;
        int dir_y = is_move_down - is_move_up;
        double len_dir = sqrt(dir_x * dir_x + dir_y * dir_y);
        if (len_dir != 0)
        {
            double normalized_x = dir_x / len_dir;
            double normalized_y = dir_y / len_dir;
            player_pos.x += (int)(PLAYER_SPEED * normalized_x);
            player_pos.y += (int)(PLAYER_SPEED * normalized_y);
        }
        if (player_pos.x < 0)player_pos.x = 0;
        if (player_pos.y < 0)player_pos.y = 0;
        if (player_pos.x + PLAYER_WIDTH > WINDOW_WIDTH)player_pos.x = WINDOW_WIDTH - PLAYER_WIDTH;
        if (player_pos.y + PLAYER_HEIGHT > WINDOW_HEIGHT)player_pos.y = WINDOW_HEIGHT - PLAYER_HEIGHT;
    }
    void Draw(int delta)
    {
        static bool facing_left = false;
        int dir_x = is_move_right - is_move_left;
        if (dir_x < 0)
            facing_left = true;
        else if (dir_x > 0)
            facing_left = false;
        if (facing_left)
            anim_left->Play(player_pos.x, player_pos.y, delta);
        else
            anim_right->Play(player_pos.x, player_pos.y, delta);
    }
    const POINT GetPosition() const
    {
        return player_pos;
    }
private:
    const int PLAYER_SPEED = 3;

    Animation* anim_left;
    Animation* anim_right;
    POINT player_pos = { 850,500 };
    bool is_move_up = false;
    bool is_move_down = false;
    bool is_move_left = false;
    bool is_move_right = false;
    bool is_fire_key_down = false;
};

class Tusk
{
public:
    Vector2 position;
    Vector2 velocity;
    IMAGE img_tusk;

    Tusk(const Vector2& start_pos, const Vector2& target_pos)
    {
        Vector2 dir = (target_pos - start_pos).normalize();
        speed = 3.0;
        velocity = dir * speed;
        position = start_pos;
        is_valid = true;

        loadimage(&img_tusk, _T("img/tusk.png"));
    }

    ~Tusk() = default;

    void set_position(const Vector2& position)
    {
        this->position = position;
    }

    const Vector2& get_position() const
    {
        return position;
    }

    void on_update(float delta)
    {
        position += velocity * delta;
        if (position.x < 0 || position.x >= WINDOW_WIDTH || position.y < 0 || position.y >= WINDOW_HEIGHT)
            is_valid = false;
    }

    void on_hit()
    {
        is_valid = false;
    }

    bool can_remove()
    {
        return !is_valid;
    }

    void Draw()
    {
        putimage_alpha((int)position.x, (int)position.y, &img_tusk);
    }

private:
    float speed;
    bool is_valid;
};

class Enemy
{
public:
    const int ENEMY_WIDTH = 30;  
    const int ENEMY_HEIGHT = 210;
public:
    Enemy()
    {
        anim_left = new Animation(_T("img/Valentine_left_%d.png"), 6, 45);
        anim_right = new Animation(_T("img/Valentine_right_%d.png"), 6, 45);

        enum class SpawnEdge
        {
            Up = 0,
            Down,
            Left,
            Right,
        };

        SpawnEdge edge = (SpawnEdge)(rand() % 4);
        switch (edge)
        {
        case SpawnEdge::Up:
            enemy_pos.x = rand() % (WINDOW_WIDTH - ENEMY_WIDTH);  
            enemy_pos.y = -ENEMY_HEIGHT;
            break;
        case SpawnEdge::Down:
            enemy_pos.x = rand() % (WINDOW_WIDTH - ENEMY_WIDTH);
            enemy_pos.y = WINDOW_HEIGHT;
            break;
        case SpawnEdge::Left:
            enemy_pos.x = -ENEMY_WIDTH;
            enemy_pos.y = rand() % (WINDOW_HEIGHT - ENEMY_HEIGHT);
            break;
        case SpawnEdge::Right:
            enemy_pos.x = WINDOW_WIDTH;
            enemy_pos.y = rand() % (WINDOW_HEIGHT - ENEMY_HEIGHT);
            break;
        default:
            break;
        }
    }

    bool CheckTuskCollision(const Tusk& tusk)
    {
        bool is_overlap_x = tusk.position.x + 20 >= enemy_pos.x && tusk.position.x <= enemy_pos.x + ENEMY_WIDTH;  
        bool is_overlap_y = tusk.position.y + 20 >= enemy_pos.y && tusk.position.y <= enemy_pos.y + ENEMY_HEIGHT;  
        return is_overlap_x && is_overlap_y;
    }

    bool CheckPlayerCollision(const Player& player)
    {
        POINT check_position = { enemy_pos.x, enemy_pos.y };
        const POINT& player_position = player.GetPosition();
        bool is_overlap_x = check_position.x + ENEMY_WIDTH >= player_position.x && check_position.x <= player_position.x + player.PLAYER_WIDTH;
        bool is_overlap_y = check_position.y + ENEMY_HEIGHT >= player_position.y && check_position.y <= player_position.y + player.PLAYER_HEIGHT;
        return is_overlap_x && is_overlap_y;
    }

    void Move(const Player& player)
    {
        const POINT& player_position = player.GetPosition();
        int dir_x = player_position.x - enemy_pos.x;
        int dir_y = player_position.y - enemy_pos.y;
        double len_dir = sqrt(dir_x * dir_x + dir_y * dir_y);
        if (len_dir > 0)
        {
            double normalized_x = dir_x / len_dir;
            double normalized_y = dir_y / len_dir;
            enemy_pos.x += (int)(ENEMY_SPEED * normalized_x);
            enemy_pos.y += (int)(ENEMY_SPEED * normalized_y);
        }
        if (dir_x < 0)
            facing_left = true;
        else if (dir_x > 0)
            facing_left = false;

        if (enemy_pos.x < 0) enemy_pos.x = 0;
        if (enemy_pos.y < 0) enemy_pos.y = 0;
        if (enemy_pos.x + ENEMY_WIDTH > WINDOW_WIDTH) enemy_pos.x = WINDOW_WIDTH - ENEMY_WIDTH;
        if (enemy_pos.y + ENEMY_HEIGHT > WINDOW_HEIGHT) enemy_pos.y = WINDOW_HEIGHT - ENEMY_HEIGHT;
    }

    void Draw(int delta)
    {
        if (facing_left)
            anim_left->Play(enemy_pos.x, enemy_pos.y, delta);
        else
            anim_right->Play(enemy_pos.x, enemy_pos.y, delta);
    }

    ~Enemy()
    {
        delete anim_left;
        delete anim_right;
    }

    const POINT GetPosition() const
    {
        return enemy_pos;
    }

    void Hurt()
    {
        alive = false;
    }

    bool CheckAlive()
    {
        return alive;
    }

private:
    const double ENEMY_SPEED = 2.5;

    Animation* anim_left;
    Animation* anim_right;
    POINT enemy_pos = { 0,0 };
    bool facing_left = false;
    bool alive = true;
};

void TryGenerateEnemy(std::vector<Enemy*>& enemy_list)
{
    const int Interval = 30;
    static int counter = 0;
    if ((++counter) % Interval == 0)
        enemy_list.push_back(new Enemy());
}

void DrawScore(int score)
{
    static TCHAR text[64];
    _stprintf_s(text, _T("当前玩家得分：%d"), score);
    setbkmode(TRANSPARENT);
    settextcolor(RGB(255, 0, 0));
    outtextxy(10, 10, text);
}

int main() {
    initgraph(1920, 1080);
    mciSendString(_T("open mus/gamebgm.mp3 alias gamebgm"), NULL, 0, NULL);
    mciSendString(_T("open mus/dongqiang.mp3 alias hit"), NULL, 0, NULL);
    int score=0;
    Player player;
    ExMessage msg;
    IMAGE img_menu;
    IMAGE img_background;
    std::vector<Enemy*> enemy_list;
    std::vector<Tusk*> tusk_list;  
    RECT region_btn_start_game, region_btn_quit_game;
    region_btn_start_game.left = 400;
    region_btn_start_game.right = region_btn_start_game.left + BUTTON_WIDTH;
    region_btn_start_game.top = 200;
    region_btn_start_game.bottom = region_btn_start_game.top + BUTTON_HEIGHT;
    region_btn_quit_game.left = 1280;
    region_btn_quit_game.right = region_btn_quit_game.left + BUTTON_WIDTH;
    region_btn_quit_game.top = 150;
    region_btn_quit_game.bottom = region_btn_quit_game.top + BUTTON_HEIGHT;
    StartGameButton btn_start_game = StartGameButton(region_btn_start_game, _T("img/startgame_up.png"), _T("img/startgame_down.png"), _T("img/startgame_up.png"));
    QuitGameButton btn_quit_game = QuitGameButton(region_btn_quit_game, _T("img/quitgame_up.png"), _T("img/quitgame_down.png"), _T("img/quitgame_up.png"));
    loadimage(&img_menu, _T("img/sbrmenu.jpg"));
    loadimage(&img_background, _T("img/sbrbackground.jpg"));
    const int FPS = 60;
    BeginBatchDraw();


    srand((unsigned)time(NULL));

    while (running) {
        DWORD frame_start_time = GetTickCount();

        while (peekmessage(&msg))
        {
            if (is_game_started) 
            {
                player.ProcessEvent(msg);
                if (msg.message == WM_LBUTTONDOWN)
                {
                    POINT player_pos = player.GetPosition();
                    Vector2 start_pos(player_pos.x + player.PLAYER_WIDTH / 2, player_pos.y + player.PLAYER_HEIGHT / 2);
                    Vector2 target_pos(msg.x, msg.y);
                    tusk_list.push_back(new Tusk(start_pos, target_pos));
                }
            }
            else
            {
                btn_start_game.ProcessEvent(msg);
                btn_quit_game.ProcessEvent(msg);
            }
        }

        if (is_game_started) {
            player.Move();
            TryGenerateEnemy(enemy_list);

            
            for (size_t i = 0; i < tusk_list.size(); ++i)
            {
                if (tusk_list[i]) {  
                    tusk_list[i]->on_update(1000.0f / FPS);
                    if (tusk_list[i]->can_remove())
                    {
                        delete tusk_list[i];
                        tusk_list.erase(tusk_list.begin() + i);
                        --i;  
                    }
                }
            }

            
            for (size_t i = 0; i < enemy_list.size(); ++i) {
                Enemy* enemy = enemy_list[i];
                if (enemy) {  
                    for (size_t j = 0; j < tusk_list.size(); ++j) {
                        Tusk* tusk = tusk_list[j];
                        if (tusk && enemy->CheckTuskCollision(*tusk)) {
                            mciSendString(_T("play hit from 0"), NULL, 0, NULL);
                            enemy->Hurt();
                            tusk->on_hit();
                            score++;
                        }
                    }
                }
            }

            
            for (Enemy* enemy : enemy_list) {
                if (enemy)  
                    enemy->Move(player);
            }

            
            for (Enemy* enemy : enemy_list)
            {
                if (enemy && enemy->CheckPlayerCollision(player))
                {
                    static TCHAR text[128];
                    _stprintf_s(text, _T("最终得分：%d ！"), score);
                    MessageBox(GetHWnd(), _T("你输了"), _T("游戏结束"), MB_OK);
                    running = false;
                    break;
                }
            }

            
            for (size_t i = 0; i < enemy_list.size(); i++)
            {
                if (enemy_list[i] && !enemy_list[i]->CheckAlive())
                {
                    delete enemy_list[i];
                    enemy_list[i] = nullptr;  
                    std::swap(enemy_list[i], enemy_list.back());
                    enemy_list.pop_back();
                }
            }
        }

        
        cleardevice();
        if (is_game_started) {
            putimage(0, 0, &img_background);
            player.Draw(1000 / FPS);  

            
            for (Enemy* enemy : enemy_list) {
                if (enemy)  
                    enemy->Draw(1000 / FPS);  
            }

            
            for (Tusk* tusk : tusk_list) {
                if (tusk)  
                    tusk->Draw();
            }

            DrawScore(score);
        }
        else
        {
            putimage(0, 0, &img_menu);
            btn_start_game.Draw();
            btn_quit_game.Draw();
        }
        FlushBatchDraw();

        
        DWORD frame_end_time = GetTickCount();
        DWORD frame_delta_time = frame_end_time - frame_start_time;
        if (frame_delta_time < 1000 / FPS)
            Sleep(1000 / FPS - frame_delta_time);
    }

    EndBatchDraw();

    for (auto tusk : tusk_list) {
        delete tusk;
    }
    for (auto enemy : enemy_list) {
        delete enemy;
    }
    return 0;
}